/*
 * FailingInputFilterStream.java
 *
 * Created on October 22, 2002, 4:21 PM
 *
 * Copyright (c) 2002 The General Electric Company
 */

package com.ge.healthcare.autosc.processor.shemjob.test;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Failing Input Filter Stream.
 *  This input stream operates as normal except that it has a limit as
 *   to the number of bytes which may be read.  When the limit is reached,
 *   the stream either acts like the end of the stream has been reached or
 *   an I/O error has been detected.
 *  It is useful for testing files that appear truncated or that fail while
 *   they are being read.
 *
 * @author Daniel S. Horwitz, Compuware Corp.
 *
 * @version AutoSC P5P6
 */
public class FailingInputFilterStream extends FilterInputStream
{
    /**
     * Number of bytes remaining which may be read successfully.  Once this
     *  value reaches zero, the failure condition is triggered.
     */
    private int bytesRemaining = 0;

    /**
     * This determines what to do when the failure condition is triggered.
     *  If true, act like the end of the stream has been reached when the
     *  failure condition is triggered.  If false, act like an I/O error
     *  has been detected when the failure condition is triggered.
     */
    private boolean failureConditionEndOfStream = false;

    /**
     * Buffer for the read() method.  This limits the number of times that
     *  this object must be instantiated.
     */
    private byte[] oneByteBuffer = new byte[1];

    /**
     * Marker returned by InputStream methods to indicate that the end of the
     *  stream has been reached.
     */
    private static final int STREAM_END_MARKER = -1;

    /**
     * Creates a FailingInputFilterStream.
     *
     * @param aSourceStream The underlying input stream.
     *
     * @param bytesUntilFailure The number of bytes which may be read
     *  successfully.  Once this limit is reached, the failure condition
     *  is triggered.
     *
     * @param endOfStreamOnFailure This determines what to do when the
     *  failure condition is triggered.
     *  If true, act like the end of the stream has been reached when the
     *  failure condition is triggered.  If false, act like an I/O error
     *  has been detected when the failure condition is triggered.
     */
    public FailingInputFilterStream( InputStream aSourceStream,
                                     int bytesUntilFailure,
                                     boolean endOfStreamOnFailure )
    {
        super( aSourceStream );
        bytesRemaining = bytesUntilFailure;
        failureConditionEndOfStream = endOfStreamOnFailure;
    }

    /**
     * How many bytes can be read from this stream without blocking?  Because
     *  it is unlikely that this method will be used, it is currently
     *  implemented as the number of bytes available without reading another
     *  source record.
     *
     * @return int
     *  The minimum number of bytes that can be read from this input stream
     *   without blocking.
     *
     * @exception IOException If an I/O error occurs.
     */
    public int available() throws IOException
    {
        final int avail = super.available();
        return Math.min( avail, bytesRemaining );
    }

    /**
     * Reads the next byte of data from this input stream.  The value byte is
     *  returned as an int in the range 0 to 255.  If no byte is available
     *  because the end of the stream has been reached, the value -1 is
     *  returned.  This method blocks until input data is available, the end
     *  of the stream is detected, or an exception is thrown.
     *
     * @return int
     *  The next byte of data, or -1 if the end of the stream is reached.
     *
     * @exception IOException If an I/O error occurs.
     */
    public int read() throws IOException
    {
        final int numBytes = read( oneByteBuffer );
        if ( numBytes < 0 )
        {
            return STREAM_END_MARKER;
        }
        final byte returnByte = oneByteBuffer[0];
        return 0x0ff & returnByte;
    }

    /**
     * Reads up to buffer.length bytes of data from this input stream into an
     *  array of bytes.  This method blocks until some input is available.
     *
     * @param buffer The buffer into which the data is read.
     *
     * @return The number of bytes read into the buffer.
     *  Returns -1 if there is no more data because the end of the stream
     *  has been reached.
     *
     * @exception IOException If an I/O error occurs.
     */
    public int read( byte[] buffer ) throws IOException
    {
        return read( buffer, 0, buffer.length );
    }

    /**
     * Reads up to buffer.length bytes of data from this input stream into an
     *  array of bytes.  This method blocks until some input is available.
     *
     * @param buffer The buffer into which the data is read.
     *
     * @param offset The starting offset of the data in the buffer.
     *
     * @param maxLength The maximum number of bytes to read.
     *
     * @return The number of bytes read into the buffer.
     *  Returns -1 if there is no more data because the end of the stream
     *  has been reached.
     *
     * @exception IOException If an I/O error occurs.
     */
    public int read( byte[] buffer, int offset, int maxLength )
        throws IOException
    {
        int numRead = super.read( buffer, offset, maxLength );
        if ( numRead <= bytesRemaining )
        {
            bytesRemaining -= numRead;
            return numRead;
        }
        if ( !failureConditionEndOfStream )
        {
            throw new IOException( "I/O exception generated by test stub" );
        }
        if ( bytesRemaining == 0 )
        {
            return STREAM_END_MARKER;
        }
        numRead = bytesRemaining;
        bytesRemaining = 0;
        return numRead;
    }

    /**
     * Skips over and discards n bytes of data from the input stream.
     *
     * @param numBytes The number of bytes to be skipped.
     *
     * @return The actual number of bytes skipped.
     *
     * @exception IOException If an I/O error occurs.
     */
    public long skip( long numBytes ) throws IOException
    {
        final long skipSize = Math.min( numBytes, bytesRemaining );
        final long numSkipped = super.skip( skipSize );
        bytesRemaining -= (int) numSkipped;
        return numSkipped;
    }
}
